﻿using System;
using System.Collections.Generic;
using System.Threading;
using Moq;
using NUnit.Framework;

namespace PoolKit.Tests
{
    [TestFixture]
    public class CollectionMinimumCountEnforcerTests
    {
        private static CollectionMinimumCountEnforcer<string> CreateCollectionMinimumCountEnforcerWithMocks()
        {
            var strategyMock = new Mock<ResourceCreationStrategy<string>>();

            strategyMock.SetupGet(x => x.ThreadingContract).Returns(ThreadingContract.Multithreaded);

            return new CollectionMinimumCountEnforcer<string>(
                new Mock<IResourceCollectionUsageTracking<string>>().Object,
                new Mock<ICommandProcessor>().Object,
                3,
                strategyMock.Object,
                1);
        }

        [Test]
        [Timeout(5000)]
        public void MakeSureCollectionCountEnforcerPumpsResourceCountToDesiredValueAfterItHasBeenStarted()
        {
            var startingResourcePool = new[] {"1", "2", "3"};
            var resourceCollection = new ResourceCollection<string>(startingResourcePool);

            using (var commandProcessorThreadPoolBased = new CommandProcessorThreadPoolBased())
            {
                const int intendedResourceCount = 20;

                using (var collectionMinimumCountEnforcer =
                    new CollectionMinimumCountEnforcer<string>(
                        resourceCollection,
                        commandProcessorThreadPoolBased,
                        intendedResourceCount,
                        new ResourceCreationFunction<string>(() => { return Guid.NewGuid().ToString("N"); }, ThreadingContract.Multithreaded),
                        1))
                {
                    collectionMinimumCountEnforcer.Start();

                    while (resourceCollection.Count != intendedResourceCount)
                    {
                        Thread.Sleep(250);
                    }

                    Thread.Sleep(250);

                    Assert.AreEqual(intendedResourceCount, resourceCollection.Count);
                }
            }
        }

        [Test]
        public void MakeSureCollectionCountEnforcerCannotBeStoppedIfItAintRunning()
        {
            CollectionMinimumCountEnforcer<string> collectionMinimumCountEnforcer = CreateCollectionMinimumCountEnforcerWithMocks();

            collectionMinimumCountEnforcer.Start();
            collectionMinimumCountEnforcer.Stop();

            Assert.Throws<InvalidOperationException>(collectionMinimumCountEnforcer.Stop);
        }

        [Test]
        public void MakeSureYouCannotDoJackWithDisposedCollectionCountEnforcer()
        {
            CollectionMinimumCountEnforcer<string> collectionMinimumCountEnforcer = CreateCollectionMinimumCountEnforcerWithMocks();

            collectionMinimumCountEnforcer.Dispose();

            DisposablePatternTester.MakeSureDisposedObjectIsUnusable(collectionMinimumCountEnforcer);
        }

        [Test]
        public void MakeSureCollectionCountEnforcerCannotBeStartedTwiceInARow()
        {
            CollectionMinimumCountEnforcer<string> collectionMinimumCountEnforcer = CreateCollectionMinimumCountEnforcerWithMocks();

            collectionMinimumCountEnforcer.Start();

            Assert.Throws<InvalidOperationException>(collectionMinimumCountEnforcer.Start);
        }

        [Test]
        [Timeout(5000)]
        public void MakeSureCollectionCountEnforcerStopsPumpingResourceCountWhenStopped()
        {
            var startingResourcePool = new[] { "1", "2", "3" };
            var resourceCollection = new ResourceCollection<string>(startingResourcePool);

            using (var commandProcessorThreadPoolBased = new CommandProcessorThreadPoolBased())
            {
                const int intendedResourceCount = 3;

                using (var collectionMinimumCountEnforcer =
                    new CollectionMinimumCountEnforcer<string>(
                        resourceCollection,
                        commandProcessorThreadPoolBased,
                        intendedResourceCount,
                        new ResourceCreationFunction<string>(() => { return Guid.NewGuid().ToString("N"); }, ThreadingContract.Multithreaded),
                        1))
                {
                    collectionMinimumCountEnforcer.Start();
                    collectionMinimumCountEnforcer.Stop();

                    while (resourceCollection.Count != 0)
                    {
                        string resource;
                        resourceCollection.TryTake(out resource);
                    }

                    // trying to wait for enough time for the enforcer to step up
                    Thread.Sleep(500);

                    Assert.AreEqual(0, resourceCollection.Count);
                }
            }
        }

        [Test]
        public void MakeSureCollectionCountEnforcerDoesNotPumpResourceCountUpWhenThereAreEnoughResourcesAlready()
        {
            var startingResourcePool = new[] { "1", "2", "3" };
            var resourceCollection = new ResourceCollection<string>(startingResourcePool);

            using (var commandProcessorThreadPoolBased = new CommandProcessorThreadPoolBased())
            {
                const int intendedResourceCount = 2;

                using (var collectionMinimumCountEnforcer =
                    new CollectionMinimumCountEnforcer<string>(
                        resourceCollection,
                        commandProcessorThreadPoolBased,
                        intendedResourceCount,
                        new ResourceCreationFunction<string>(() => { return Guid.NewGuid().ToString("N"); }, ThreadingContract.Multithreaded),
                        1))
                {
                    collectionMinimumCountEnforcer.Start();

                    // trying to wait for enough time for the enforcer to step up
                    Thread.Sleep(500);

                    Assert.AreEqual(startingResourcePool.Length, resourceCollection.Count);
                }
            }
        }

        [Test]
        public void MakeSureSeveralCommandsCanPumpTheCountUpJustFine()
        {
            var startingResourcePool = new[] { "1", "2", "3" };
            var resourceCollection = new ResourceCollection<string>(startingResourcePool);

            using (var commandProcessorThreadPoolBased = new CommandProcessorThreadPoolBased())
            {
                const int intendedResourceCount = 1000000;

                using (var collectionMinimumCountEnforcer =
                    new CollectionMinimumCountEnforcer<string>(
                        resourceCollection,
                        commandProcessorThreadPoolBased,
                        intendedResourceCount,
                        new ResourceCreationFunction<string>(() => { return Guid.NewGuid().ToString("N"); }, ThreadingContract.Multithreaded),
                        10))
                {
                    collectionMinimumCountEnforcer.Start();

                    while (resourceCollection.Count < intendedResourceCount)
                    {
                        for (int j = 0; j < 150000; j++)
                        {
                            if (resourceCollection.Count < intendedResourceCount)
                            {
                                break;
                            }

                            string resource;
                            resourceCollection.TryTake(out resource);
                        }
                        
                        Thread.Sleep(250);
                    }
                }
            }
        }

        [Test]
        public void MakeSureEnforcerDoesItsJobUnderLoad()
        {
            var startingResourcePool = new[] { "1", "2", "3" };
            var resourceCollection = new ResourceCollection<string>(startingResourcePool);

            using (var commandProcessorThreadPoolBased = new CommandProcessorThreadPoolBased())
            {
                const int intendedResourceCount = 200;

                using (var collectionMinimumCountEnforcer =
                    new CollectionMinimumCountEnforcer<string>(
                        resourceCollection,
                        commandProcessorThreadPoolBased,
                        intendedResourceCount,
                        new ResourceCreationFunction<string>(() => { return Guid.NewGuid().ToString("N"); }, ThreadingContract.Multithreaded),
                        3))
                {
                    collectionMinimumCountEnforcer.Start();

                    var consumingThreads = new List<Thread>();

                    var threadStartEvent = new Barrier(20);

                    for (int i = 0; i < 20; i++)
                    {
                        var thread = new Thread(delegate()
                                       {
                                           threadStartEvent.SignalAndWait();

                                           for (int j = 0; j < 10000; j++)
                                           {
                                               string resource;
                                               resourceCollection.TryTake(out resource);
                                           }
                                       });

                        thread.Start();

                        consumingThreads.Add(thread);
                    }

                    foreach (Thread consumingThread in consumingThreads)
                    {
                        consumingThread.Join();
                    }

                    Thread.Sleep(TimeSpan.FromSeconds(2));

                    Assert.IsTrue(intendedResourceCount <= resourceCollection.Count);
                }
            }
        }
    }
}